from py2neo import Node, Relationship, NodeMatcher, RelationshipMatcher
from itertools import groupby


class LogicalTopo:
    """运维对象自动生成逻辑拓扑图"""

    Services = ['api', 'elasticsearch', 'mysql', 'service', 'other']

    def __init__(self, client, label, nodes):
        self.label = label
        self.nodes = nodes
        self.client = client

    def parser(self):
        """获取所有实体
        机房
        机架
        服务器 服务 数据库 xxx
        """
        entity_machine_rooms = []
        entity_machine_frames = []
        entity_relations = []

        entity_devops = []  # 所有运维对象

        services = [node for node in self.nodes if node.get('type') in self.Services]  # 所有软件服务
        hosts = [node for node in self.nodes if node.get('type') not in self.Services]  # 所有硬件设施

        service_nodes = {service['id']: Node(self.label, dev_ops_id=service['id'], type=service['type'],
                                             name=service['name'], **service['attr']) for service in services}
        entity_devops.extend(list(service_nodes.values()))

        hosts.sort(key=lambda x: (x['attr'].get('machine_room', ''), x['attr'].get('machine_frame', '')))
        for machine_room, room_devops in groupby(hosts, key=lambda x: x['attr'].get('machine_room', '')):
            if machine_room:
                room_node = Node(self.label, type='machine_room', name=machine_room)
                entity_machine_rooms.append(room_node)
            else:
                room_node = None

            for machine_frame, frame_devops in groupby(room_devops, key=lambda x: x['attr'].get('machine_frame', '')):
                if machine_frame:
                    frame_node = Node(self.label, type='machine_frame', name=machine_frame)
                    entity_machine_frames.append(frame_node)
                else:
                    frame_node = None

                if machine_room and machine_frame:
                    entity_relations.append(Relationship(room_node, '承载', frame_node, label=self.label))

                for hardware in frame_devops:
                    entity_hardware = Node(self.label, dev_ops_id=hardware['id'], type=hardware['type'],
                                           name=hardware['name'], **hardware['attr'])
                    entity_devops.append(entity_hardware)
                    if frame_node:
                        entity_relations.append(Relationship(frame_node, '承载', entity_hardware, label=self.label))
                    elif room_node:
                        entity_relations.append(Relationship(room_node, '承载', entity_hardware, label=self.label))

                    # 硬件上跑的服务 IP唯一关联(服务没有机房标签的)
                    if ip := hardware['attr'].get('ip'):
                        for service in services:
                            if service['attr'].get('ip') == ip:
                                entity_devop_ = service_nodes.get(service['id'])
                                entity_relations.append(Relationship(entity_hardware, '承载', entity_devop_,
                                                                     label=self.label))

        return entity_machine_rooms, entity_machine_frames, entity_devops, entity_relations

    def execute(self):
        entity_machine_rooms, entity_machine_frames, entity_devops, entity_relations = self.parser()
        s = self.client.graph.begin()
        try:
            s.run(f"match (n:`{self.label}`) detach delete n")

            [s.create(entity) for entity in entity_machine_rooms]
            [s.create(entity) for entity in entity_machine_frames]
            [s.create(entity) for entity in entity_devops]
            [s.create(entity) for entity in entity_relations]
            self.client.graph.commit(s)
        except Exception as e:
            s.rollback()
            print(e)

    def node_match(self, filters=None, size=100):
        match = NodeMatcher(self.client.graph).match(self.label).limit(size).all()
        return match

    def relation_match(self, nodes=None, size=100):
        match = RelationshipMatcher(self.client.graph).match(r_type=None).where(label=self.label).limit(size).all()
        return match


if __name__ == '__main__':
    from common.package.neo4j_db import neo4j_db

    neo4j_db.init(host="192.168.101.80", port=7687, auth=("neo4j", "MY.io2019"))

    dev_ops = [
        {'id': '1', 'name': '服务器1', 'type': 'server', 'attr': {'ip': '192.168.100.3', 'machine_room': '成都机房', 'machine_frame': '机架1'}},
        {'id': '2', 'name': '主机1', 'type': 'server', 'attr': {'ip': '192.168.101.79', 'machine_room': '上海机房', 'machine_frame': '机架2'}},
        {'id': '3', 'name': '主机1', 'type': 'server', 'attr': {'ip': '192.168.101.5', 'machine_room': '西安机房'}},
        {'id': '4', 'name': '主机5', 'type': 'server', 'attr': {'ip': '192.168.101.4'}},

        {'id': '41', 'name': 'mysql3', 'type': 'mysql', 'attr': {'ip': '192.168.101.4', 'port': 3306, 'account': 'xxx', 'password': 'xxx'}},

        {'id': '31', 'name': 'mysql1', 'type': 'mysql', 'attr': {'ip': '192.168.101.5', 'port': 3306, 'account': 'xxx', 'password': 'xxx'}},
        {'id': '11', 'name': 'mysql2', 'type': 'mysql', 'attr': {'ip': '192.168.100.3', 'port': 3306, 'account': 'xxx', 'password': 'xxx'}},

        {'id': '21', 'name': 'api1', 'type': 'api', 'attr': {'ip': '192.168.101.79', 'port': 8003}},
        {'id': '12', 'name': 'api2', 'type': 'api', 'attr': {'ip': '192.168.100.3', 'port': 8004}},
        {'id': '22', 'name': 'api2', 'type': 'api', 'attr': {'ip': '192.168.101.79', 'port': 8005}},

        {'id': '13', 'name': 'elasticsearch1', 'type': 'elasticsearch', 'attr': {'ip': '192.168.100.3', 'port': 9200,
                                                                                 'account': 'xxx', 'password': 'xxx'}},
    ]

    logical_topo = LogicalTopo(neo4j_db, label='logical_topo', nodes=dev_ops)
    logical_topo.execute()
    nodes = logical_topo.node_match()
    relations = logical_topo.relation_match(nodes=nodes)
    print(nodes, relations)
